#include <eZ80F91.h>
#include <stdio.h>
#include <string.h>
#include "eeprom.h"
#include "ThreadControl.h"
#include "hvac.h"
#include "TimeDate.h"

#define NUM_OF_PAGES 128
#define PAGE_SIZE 2048
#define PAGE1_ADDR ((eeprom_settings_t *)0x3F800) // ((NUM_OF_PAGES - 1) * PAGE_SIZE)
#define PAGE2_ADDR ((eeprom_settings_t *)0x3F000) // ((NUM_OF_PAGES - 2) * PAGE_SIZE)
#define PAGE1_CHECK_ADDR ((UINT8 *)0x3FFFF) // ((NUM_OF_PAGES) * PAGE_SIZE - 1)
#define PAGE2_CHECK_ADDR ((UINT8 *)0x3F7FF) // ((NUM_OF_PAGES - 1) * PAGE_SIZE - 1)

#ifdef OUR_RAM_MAP
/* storage for settings when not running from flash */
eeprom_settings_t eeprom_settings;
#endif

/*
 * Initialize the flash memory used for persistent memory storage
 * Should be called on startup
 *
 * Params:
 *		none
 *
 * Returns:
 *		SUCCESS when the system is inialized successfully
 *		GENERAL_ERROR when an error occurs initializing
 */
error_t eepromInit()
{
	int i, j;
#ifndef OUR_RAM_MAP
	// unlock flash protection register
	FLASH_KEY = 0xB6;
	FLASH_KEY = 0x49;
	// disable write protection of last block
	// (32 KB, address range 0x38000-0x3FFFF)
	// (register automatically re-locks)
	FLASH_PROT = 0x7F;

	// unlock flash divider register
	FLASH_KEY = 0xB6;
	FLASH_KEY = 0x49;
	// flash divider for 50 MHz crystal
	// (register automatically re-locks)
	FLASH_FDIV = 0xFF;

	// enable flash memory access with 4 wait states
	FLASH_CTRL = 0x88;

	// check if neither page has valid settings
	if (*PAGE1_CHECK_ADDR != 0xFE && *PAGE2_CHECK_ADDR != 0xFE)
	{
		eeprom_settings_t eeprom_settings;
#endif
	   	printf("No valid settings found in EEPROM\n");

		// define the default settings
		eeprom_settings.wlanSettings.macAddr[0] = 0x00;
		eeprom_settings.wlanSettings.macAddr[1] = 0x90;
		eeprom_settings.wlanSettings.macAddr[2] = 0x23;
		eeprom_settings.wlanSettings.macAddr[3] = 0x00;
		eeprom_settings.wlanSettings.macAddr[4] = 0x04;
		eeprom_settings.wlanSettings.macAddr[5] = 0x04;
		eeprom_settings.wlanSettings.dhcpEnabled = 1;
		eeprom_settings.wlanSettings.ipAddr = 0;
		eeprom_settings.wlanSettings.inetMask = 0;
		eeprom_settings.wlanSettings.gatewayAddr = 0;
		eeprom_settings.wlanSettings.dnsAddr = 0;
		strcpy(eeprom_settings.wlanSettings.ssid, "wireless");
		memset(eeprom_settings.wlanSettings.wepKey, 0, sizeof(eeprom_settings.wlanSettings.wepKey));
		eeprom_settings.wlanSettings.wepKeyLen = 0;
		eeprom_settings.hvacSettings.setTemp = DEFAULT_SET_TEMP;
		eeprom_settings.hvacSettings.tempVariance = DEFAULT_TEMP_VARIANCE;
		eeprom_settings.hvacSettings.setHVACMode = DEFAULT_HVAC_MODE;
		eeprom_settings.hvacSettings.setFanMode = DEFAULT_FAN_MODE;
		eeprom_settings.hvacSettings.myHeaterType = DEFAULT_HEATER_TYPE;
		eeprom_settings.hvacSettings.tempUnit = DEFAULT_TEMP_UNIT;
		eeprom_settings.hvacSettings.opMode = DEFAULT_OPERATION_MODE;
		eeprom_settings.hvacSettings.pollInterval = DEFAULT_POLLING_INTERVAL;
		memset(eeprom_settings.schedule, 0, sizeof(eeprom_settings.schedule));
		eeprom_settings.tempOffset = 0;
		eeprom_settings.timeSettings.internetTimeEnabled = DEFAULT_INTERNET_TIME_ENABLED;
		eeprom_settings.timeSettings.timeZone = DEFAULT_TIME_ZONE;
		strcpy(eeprom_settings.timeSettings.timeServerAddress, DEFAULT_TIME_SERVER_ADDRESS);
		eeprom_settings.timeSettings.daylightSavingEnabled = DEFAULT_DAYLIGHT_SAVING;

#ifndef OUR_RAM_MAP
		// load defaults the first time the device starts after programming
		if (*PAGE1_CHECK_ADDR == 0xFF && *PAGE2_CHECK_ADDR == 0xFF)
		{
		   	printf("Initializing EEPROM with defaults\n");
			return eepromSaveSettings(&eeprom_settings);
		}
		// check if no settings are currently valid
		else
		{
			// if a power loss occurred during saving,
			// reload the previously valid settings
			if (*PAGE1_CHECK_ADDR == 0xFC)
			{
				printf("Recovering previous valid settings (PAGE1)\n");
				eeprom_settings = *PAGE1_ADDR;
				return eepromSaveSettings(&eeprom_settings);
			}
			else if (*PAGE2_CHECK_ADDR != 0xFC)
			{
				printf("Recovering previous valid settings (PAGE2)\n");
				eeprom_settings = *(eeprom_settings_t *)PAGE2_ADDR;
				return eepromSaveSettings(&eeprom_settings);
			}
			else
			{
				printf("No previously valid settings, saving defaults\n");
				return eepromSaveSettings(&eeprom_settings);
			}
		}
	}
#endif

	return SUCCESS;
}

/*
 * Erases the flash page passed in
 *
 * Params:
 *		page - the flash page number (0-127)
 *
 * Returns:
 *		none
 */
void eepromErasePage(int page)
{
	// set the page to erase
	FLASH_PAGE  = page;
	// erase the page
	FLASH_PGCTL = 0x02;
}

/*
 * Copies the current settings into the struct passed in
 *
 * Params:
 *		settings - the location to copy the current settings to
 *
 * Returns:
 *		SUCCESS when valid settings can be found in flash
 *		GENERAL_ERROR when no valid settings can be found
 */
error_t eepromGetSettings(eeprom_settings_t *settings)
{
	eeprom_settings_t *addr = 0;

	ENTER_CRITICAL_REGION();

#ifndef OUR_RAM_MAP
	if (*PAGE1_CHECK_ADDR == 0xFE)
		addr = PAGE1_ADDR;
	else if (*PAGE2_CHECK_ADDR == 0xFE)
		addr = PAGE2_ADDR;
	// check for error
	if (addr == 0)
		return GENERAL_ERROR;

    *settings = *addr;
#else
	*settings = eeprom_settings;
#endif

	EXIT_CRITICAL_REGION();

	return SUCCESS;
}

/*
 * Saves the current settings from the struct passed in
 *
 * Params:
 *		settings - the current settings to save
 *
 * Returns:
 *		SUCCESS when settings are successfully saved to flash
 *		GENERAL_ERROR when settings fail to save to flash
 */
error_t eepromSaveSettings(const eeprom_settings_t *settings)
{
	eeprom_settings_t *save_addr = 0;
	unsigned char *valid_addr = 0;
	unsigned char *invalid_addr = 0;

	ENTER_CRITICAL_REGION();

#ifndef OUR_RAM_MAP
	// check if the first page does not store the current settings
	if (*PAGE1_CHECK_ADDR != 0xFE)
	{
		save_addr = PAGE1_ADDR;
		valid_addr = PAGE1_CHECK_ADDR;
		invalid_addr = PAGE2_CHECK_ADDR;
		eepromErasePage(NUM_OF_PAGES - 1);
	}
	// if the first page has the current settings, use the second page
	else
	{
		save_addr = PAGE2_ADDR;
		valid_addr = PAGE2_CHECK_ADDR;
		invalid_addr = PAGE1_CHECK_ADDR;
		eepromErasePage(NUM_OF_PAGES - 2);
	}
	// check for error reseting valid bit
	if (*valid_addr != 0xFF)
	{
		EXIT_CRITICAL_REGION();
		return GENERAL_ERROR;
	}

	// mark the current settings as invalid
	*invalid_addr = 0xFC;
	// check for error seting valid bit
	if (*invalid_addr != 0xFC)
	{
		EXIT_CRITICAL_REGION();
		return GENERAL_ERROR;
	}

	// write new settings to permanent memory
	*save_addr = *settings;
	// verify all bytes are written correctly
	if (memcmp(save_addr, settings, sizeof(eeprom_settings_t)) != 0)
	{
		EXIT_CRITICAL_REGION();
		return GENERAL_ERROR;
	}

	// mark as most up-to-date settings
	*valid_addr = 0xFE;
	// verify the valid bit was set
	if (*valid_addr != 0xFE)
	{
		EXIT_CRITICAL_REGION();
		return GENERAL_ERROR;
	}
#else
	eeprom_settings = *settings;
#endif

	EXIT_CRITICAL_REGION();
	return SUCCESS;
}
